package dnavsim.bed;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;
import java.util.Vector;

public class BEDHandler {

	private HashMap<String, BEDFeature[]> features = new HashMap<String, BEDFeature[]>();

	/**
	 * Optimization.
	 * Keep track of the last feature index found.
	 */
	private int lastFeatureFoundIndex = 0;
	
	/**
	 * Open, read and parse a BED file.
	 * 
	 * @param bedFile
	 * @throws IOException
	 * @throws BEDFormatException
	 */
	public BEDHandler(File bedFile) throws IOException, BEDFormatException {

		HashMap<String, Vector<BEDFeature>> f = new HashMap<String, Vector<BEDFeature>>();
		
		BufferedReader in = null;
		in = new BufferedReader(new FileReader(bedFile));
		String line;

		int l = 0;
		while ((line = in.readLine()) != null) {
			l++;			
			String chrom = null, chromStart = null, chromEnd = null;
			String name = null, score = null, strand = null, thickStart = null, thickEnd = null, blockCount = null, blockSizes = null, blockStarts = null;
			StringTokenizer st = new StringTokenizer(line, "\t");

			// Mandatory fields
			if (!st.hasMoreTokens())
				throw new BEDFormatException(
						"Missing chrom field at line " + l, null);
			chrom = st.nextToken();
			if (chrom.startsWith("chr"))
				chrom = new String(chrom.substring(3));
			if (!st.hasMoreTokens())
				throw new BEDFormatException(
						"Missing chromStart field at line " + l, null);
			chromStart = st.nextToken();
			if (!st.hasMoreTokens())
				throw new BEDFormatException("Missing chromEnd field at line "
						+ l, null);
			chromEnd = st.nextToken();

			// Optional fields
			if (st.hasMoreTokens())
				name = st.nextToken();
			if (st.hasMoreTokens())
				score = st.nextToken();
			if (st.hasMoreTokens())
				strand = st.nextToken();
			if (st.hasMoreTokens())
				thickStart = st.nextToken();
			if (st.hasMoreTokens())
				thickEnd = st.nextToken();
			if (st.hasMoreTokens())
				blockCount = st.nextToken();
			if (st.hasMoreTokens())
				blockSizes = st.nextToken();
			if (st.hasMoreTokens())
				blockStarts = st.nextToken();

			BEDFeature feat = new BEDFeature(chrom, chromStart, chromEnd, name,
					score, strand, thickStart, thickEnd, blockCount,
					blockSizes, blockStarts);
		
			// Create an entry for this chromosome in the HashMap if it does not exist yet
			Vector<BEDFeature> v = f.get(chrom);
			if (v == null) {
				v = new Vector<BEDFeature>();
				f.put(chrom, v);
			}
			
			// Then add the BED feature to the corresponding chromosome vector
			// ordering from the lowest to highest start pos
			if (v.isEmpty()) {
				v.add(feat);
			}
			else {
				boolean added = false;
//				for (int i=0; i<v.size(); i++) {
//					if (feat.getChromStart() <= v.get(i).getChromStart()) {
//						v.add(i, feat);
//						added = true;
//					}
//				}				
//				if (!added) v.add(feat);
				for (int i=v.size()-1; i>=0; i--) {
					if (feat.getChromStart() > v.get(i).getChromStart()) {
						v.add(i+1, feat);
						added = true;
						break;
					}						
				}
				if (!added) v.add(0, feat);
			}

			
		}
		in.close();
		
		// Transform vector to array (optimization)
		for (Iterator<String> itr = f.keySet().iterator(); itr.hasNext(); ) {
			String key = itr.next();
			features.put(key, f.get(key).toArray(new BEDFeature[1]));
		}
		
	}

	/**
	 * Return the first feature that contains a chromosome locus.
	 * 
	 * @param chrom
	 *            the chromosome of the locus. Chromosome names can be given
	 *            with or without the 'chr' prefix
	 * @param pos
	 *            the position of the locus in the chromosome
	 * @return true or false
	 */
	public boolean hasFeature(String chrom, int pos) {
		return getFeature(chrom, pos) != null;
	}
	
	/**
	 * Return the first feature that comprises a locus
	 * 
	 * @param chrom chromosome name (either "chr1" or "1")
	 * @param pos position in the chromosome
	 * @return the BEDFeature found or null if not found
	 */
	public BEDFeature getFeature(String chrom, int pos) {
		String chr = chrom;
		if (chr.startsWith("chr"))
			chr = new String(chr.substring(3));
		
		BEDFeature[] f = features.get(chr);
		if (f == null)
			return null;
		
		for (int i=lastFeatureFoundIndex; i<f.length; i++) {
			if (f[i].getChromStart() <= pos && pos <= f[i].getChromEnd()) {
				lastFeatureFoundIndex = i;
				return f[i];
			}
			if (f[i].getChromStart() > pos)
				break;
		}
		return null;		
	}
	
	/**
	 * Return the feature that starts or immediately follows a given position.
	 * 
	 * @param chrom chromosome name (either "chr1" or "1")
	 * @param pos position in the chromosome
	 * @return the BEDFeature found or null if not found
	 */
	public BEDFeature getNextFeature(String chrom, int pos) {
		String chr = chrom;
		if (chr.startsWith("chr"))
			chr = new String(chr.substring(3));
		
		BEDFeature[] f = features.get(chr);
		if (f == null)
			return null;

		for (int i=0; i<f.length; i++) {
			if (f[i].getChromStart() >= pos)
				return f[i];
		}
		
		return null;
	}
	
	
	/**
	 * Return the number of features registered for a given chromosome
	 * 
	 * @param chrom the chromosome to look for
	 * @return the number of features registered
	 */
	public int getFeaturesCount(String chrom) {
		if (features.get(chrom) != null)
			return features.get(chrom).length;
		else
			return 0;
	}
	
	/**
	 * @param chrom the chromosome to look for
	 * @return an array of BEDFeature registered for the chromosome in parameter
	 */
	public BEDFeature[] getFeatures(String chrom) {
		return features.get(chrom);
	}
	
}
